/**
 * @author harry
 * @date 2022/3/2/9:56
 */

package com.serviceOrchestration.factory;

import com.serviceOrchestration.annotation.AtomicParam;
import com.serviceOrchestration.annotation.AtomicService;
import com.serviceOrchestration.exception.ServerException;
import com.serviceOrchestration.atomic.AtomicUnit;
import com.serviceOrchestration.atomic.AtomicUnitInfo;
import com.serviceOrchestration.atomic.AtomicUnitParam;
import com.serviceOrchestration.util.ContextHolder;
import com.serviceOrchestration.util.InputType;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;

@Component
@Slf4j
public class AtomicFactory {

    public static final String BEAN_PREFIX = "atomicUnit";

    /**
     * 获取所有的原子服务信息，一般由管理端调用，展示所有原子服务信息，之后再有其他业务接口调用。
     * @return 所有的原子服务信息
     */
    public List<AtomicUnitInfo> getAtomicsInfo() {
        int atomicId = 0;
        ArrayList<AtomicUnitInfo> infoList = new ArrayList<>();
        while(true) {
            ++atomicId;
            AtomicUnit unit = ContextHolder.getBean(BEAN_PREFIX + atomicId);
            if(unit == null) break;
            AtomicUnitInfo info = getAtomicInfo(unit);
            List<AtomicUnitParam> params = getAtomicParams(unit);
            info.setParams(params);
            infoList.add(info);
        }
        return infoList;
    }

    /**
     * 获取原子服务的功能介绍
     * @param unit bean
     * @return id, name, detail
     */
    public AtomicUnitInfo getAtomicInfo(AtomicUnit unit) {
        AtomicService annotation = unit.getClass().getAnnotation(AtomicService.class);
        String[] split = (annotation.value()).split(BEAN_PREFIX);
        if(split.length != 2) {
            throw new ServerException("atomicUnit bean name error");
        }
        String id = split[1];
        String name = annotation.name();
        String detail = annotation.description();
        return new AtomicUnitInfo(Integer.parseInt(id), name, detail);
    }

    /**
     * 获取原子服务的配置参数说明
     * @param unit bean
     * @return 中文名，英文名，取值限制说明（可能直接是正则），取值范围（若是离散值）
     * <p>如果没有参数，返回null</p>
     */
    @SuppressWarnings("unchecked")
    public List<AtomicUnitParam> getAtomicParams(AtomicUnit unit) {
        ArrayList<AtomicUnitParam> paramList = new ArrayList<>();
        Field[] fields = unit.getClass().getDeclaredFields();
        for(Field f : fields) {
            f.setAccessible(true);
            AtomicParam annotation = f.getAnnotation(AtomicParam.class);
            if(annotation != null) {
                String zhName = annotation.zhName();
                String enName = annotation.enName();
                String type = annotation.type();
                List<String> constraints = Arrays.asList(annotation.constraints());
                if(type.equals(InputType.MULTI_SELECTION) || type.equals(InputType.SINGLE_SELECTION)) {
                    String[] classAndMethod = annotation.repository().split("\\.");
                    try {
                        Object dao = ContextHolder.getBean(classAndMethod[0]);
                        Method method = Objects.requireNonNull(dao).getClass().getDeclaredMethod(classAndMethod[1]);
                        method.setAccessible(true);
                        constraints = (List<String>) method.invoke(dao);
                    } catch (NullPointerException e) {
                        throw new ServerException("no dao bean named " + classAndMethod[0]);
                    } catch (NoSuchMethodException e) {
                        throw new ServerException("no method named " + classAndMethod[1]);
                    } catch (InvocationTargetException | IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }
                paramList.add(new AtomicUnitParam(zhName, enName, type, constraints));
            }
        }
        return paramList.isEmpty() ? null : paramList;
    }

    /**
     * 构造原子服务责任链。参数从HttpRequest中来，包含原子服务 {@code id} 和可选的配置参数 {@code params} 。
     * 根据原子服务的固定前缀 {@code BEAN_PREFIX} 拼接上 {@code id} 即得到bean的名字。
     * 调用了 {@code ContextHolder.getBean()} 方法从容器中获取bean，并配置参数（如果有）。
     * 构造出原子服务链表后返回链表的头节点，若中途出现错误即返回 {@code null}。
     * @param services HttpRequest中的参数，包括原子服务id和相应可选配置参数params
     * @return 原子服务链表头节点或 {@code null}
     */
    @SuppressWarnings("unchecked")
    public AtomicUnit makeChain(List<Map<String, Object>> services) {
        List<AtomicUnit> tmpList = new ArrayList<>();
        for(Map<String, Object> s : services) {
            Integer id = (Integer) s.get("id");
            Map<String, Object> params = (Map<String, Object>) s.get("params");
            AtomicUnit unit = makeUnit(id, params);
            if(unit == null) return null;
            tmpList.add(unit);
        }
        if(tmpList.size() == 0) {
            return null;
        }
        AtomicUnit head = tmpList.get(0);
        for(int i = 1; i < tmpList.size(); ++i) {
            head.setNextUnit(tmpList.get(i));
        }
        return head;
    }

    public AtomicUnit makeUnit(int id, Map<String, Object> params) {
        AtomicUnit unit = ContextHolder.getBean(BEAN_PREFIX + id);
        // 如果没有这个bean，说明参数错误
        if(unit == null) {
            log.error("no such AtomicUnit: id = " + id);
            return null;
        }
        // 设置原子参数，如果抛出异常也是说明参数错误
        try {
            unit.setParams(params);
        } catch (IllegalArgumentException e) {
            log.error("AtomicUnit params error: " + e.getMessage());
            return null;
        }
        return unit;
    }
}
